﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using SGD;
using System.IO;



namespace TLOW_MapEditor
{
    public partial class MainForm : Form
    {
        #region GLOBAL_VARIABLES
        // global textureManager instance
        ManagedDirect3D D3D = ManagedDirect3D.Instance;
        ManagedTextureManager TM = ManagedTextureManager.Instance;

        // Map / Tile variables //
        CTile[,] mapData = new CTile[10, 10];
        Size map_width_height = new Size(320, 320); 
        Size tile_width_height = new Size(32, 32);
        Point mapOffset = new Point();
        bool bIsSaved, bHasGridLines = true;

        // Tileset variables //
        Bitmap tilesetImage;
        Point tilesetOffset = new Point();
        string tilesetFilePath;
        int tilesetID = -1, currTile = -1;
        float version = 1.0f;

        // Undo and Redo List //
        Stack<CTile> undoMoves = new Stack<CTile>(), redoMoves = new Stack<CTile>();
        #endregion


        // SETUP FUNCTIONS //
        public MainForm()
        {
            InitializeComponent();

            D3D.InitManagedDirect3D(map_Panel, true);
            TM.InitManagedTextureManager(D3D.Device, D3D.Sprite);
        }
        public void Init()
        {
            for (int x = 0; x < 10; ++x)
                for (int y = 0; y < 10; ++y)
                    mapData[x, y] = new CTile();

            gridLinesMenuItem.Checked = true;
            undoMenuItem.Enabled = redoMenuItem.Enabled = deleteTilesetMenuItem.Enabled = false;

            // resize the form so the tileset isn't there
            MainForm.ActiveForm.Size = new Size(934, 612);

            // Scrollbar offsets //
            mapHScrollBar.Maximum = map_width_height.Width + tile_width_height.Width;
            mapVScrollBar.Maximum = map_width_height.Height + tile_width_height.Height;
        }

        // MENU ITEMS //
        private void newMenuItem_Click(object sender, EventArgs e)
        {
            // Map / Tile variables //
            mapData = new CTile[10, 10];
            for (int x = 0; x < 10; ++x)
                for (int y = 0; y < 10; ++y)
                    mapData[x, y] = new CTile();

            map_width_height = new Size(320, 320);
            tile_width_height = new Size(32, 32);
            bIsSaved = false;
            bHasGridLines = true;

            // Tileset variables //
            tilesetFilePath = "";
            tilesetID = currTile = -1;

            // Undo and Redo List //
            undoMoves = redoMoves = new Stack<CTile>();

            // Reset the numeric boxes to default values
            mapWidth.Value   = 10;
            mapHeight.Value  = 10;
            tileWidth.Value  = 32;
            tileHeight.Value = 32;
            isCollidableBox.Checked = false;

            // resize the form so the tileset isn't there
            MainForm.ActiveForm.Size = new Size(934, 612);
            deleteTilesetMenuItem.Enabled = false;

            // Scrollbar Offsets //
            mapHScrollBar.Maximum = map_width_height.Width + tile_width_height.Width;
            mapVScrollBar.Maximum = map_width_height.Height + tile_width_height.Height;
        }
        private void openMenuItem_Click(object sender, EventArgs e)
        {
            ///////////////////////////////////////////////
            //                File Format                //
            ///////////////////////////////////////////////
            //- version          - float  (4 bytes)      //
            //- tileSetID        - int    (4 bytes)      //
            //- Filepath length  - int    (4 bytes)      //
            //- tileset Filepath - string (? bytes)      //
            //- Map Width        - int    (4 bytes)      //
            //- Map Height       - int    (4 bytes)      //
            //- Tile Width       - int    (4 bytes)      //
            //- Tile Height      - int    (4 bytes)      //
            //                                           //
            //  mapData[]                                //
            //  for (int x = 0; x < MapWidth; ++x)       //
            //      for (int y = 0; y < MapHeight; ++y)  //
            //                                           //
            //- tileImageID      - int    (4 bytes)      //
            //- isCollidable     - bool   (1 byte)       //
            ///////////////////////////////////////////////

            OpenFileDialog ofd = new OpenFileDialog();

            // Set the filter strings
            ofd.Filter = "TLOW Map(*.TLOWM)|*.TLOWM;";

            if (DialogResult.OK == ofd.ShowDialog())
            {
                // clear out the old tileset if there is one
                deleteTilesetMenuItem_Click(sender, e);


                FileStream stream = new FileStream(ofd.FileName, FileMode.Open);
                BinaryReader binReader = new BinaryReader(stream);

                int filepathLen = 0, nXTiles, nYTiles, tileImageID;
                bool isCollidable;

                version = (float)binReader.ReadInt32();
                tilesetID = binReader.ReadInt32();

                if (tilesetID != -1)
                {
                    filepathLen = binReader.ReadInt32();
                    tilesetFilePath = binReader.ReadString();
                }

                nXTiles = binReader.ReadInt32();
                nYTiles = binReader.ReadInt32();
                tile_width_height.Width = binReader.ReadInt32();
                tile_width_height.Height = binReader.ReadInt32();

                CTile[,] tempMapData = new CTile[nXTiles, nYTiles];
                for (int x = 0; x < nXTiles; ++x)
                {
                    for (int y = 0; y < nYTiles; ++y)
                    {
                        tileImageID = binReader.ReadInt32();
                        isCollidable = binReader.ReadBoolean();
                        tempMapData[x, y] = new CTile(tileImageID, isCollidable, false);
                    }
                }

                binReader.Close();
                stream.Close();
                bIsSaved = true;

                mapData = tempMapData;
                #region LOAD_TILESET
                if (tilesetID != -1)
                {
                    // Load the new bitmap
                    tilesetID = TM.LoadTexture(tilesetFilePath, 0);
                    tilesetImage = new Bitmap(tilesetFilePath);
                    currTile = -1;

                    // Empty out the undo/redo list
                    Stack<CTile> undoMoves = new Stack<CTile>();
                    Stack<CTile> redoMoves = new Stack<CTile>();


                    // resize the form so the tileset is there
                    MainForm.ActiveForm.Size = new Size(934, 935);
                    deleteTilesetMenuItem.Enabled = true;

                    // Scrollbar Offsets //
                    tilesetHScrollBar.Maximum = tilesetImage.Width;
                    tilesetVScrollBar.Maximum = tilesetImage.Height;
                }
                #endregion

                mapWidth.Value      = nXTiles;
                mapHeight.Value     = nYTiles;
                tileWidth.Value     = tile_width_height.Width;
                tileHeight.Value    = tile_width_height.Height;


                // Ti
                resizeMapAndTiles_Click(sender, e);
            }
        }
        private void importTilesetMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Image Files(*.BMP;*.JPG;*.PNG)|*.BMP;*.JPG;*.PNG";

            if (DialogResult.OK == ofd.ShowDialog())
            {
                // Release the old bitmap
                if (tilesetID != -1)
                    TM.ReleaseTexture(tilesetID);

                // Load the new bitmap
                tilesetID = TM.LoadTexture(ofd.FileName, 0);
                tilesetFilePath = ofd.FileName;
                tilesetImage = new Bitmap(ofd.FileName);
                currTile = -1;

                // Empty out the undo/redo list
                Stack<CTile> undoMoves = new Stack<CTile>();
                Stack<CTile> redoMoves = new Stack<CTile>();


                // resize the form so the tileset is there
                MainForm.ActiveForm.Size = new Size(934, 935);
                deleteTilesetMenuItem.Enabled = true;

                // Scrollbar Offsets //
                tilesetHScrollBar.Maximum = tilesetImage.Width;
                tilesetVScrollBar.Maximum = tilesetImage.Height;
            }
        }
        private void deleteTilesetMenuItem_Click(object sender, EventArgs e)
        {
            if (tilesetID == -1)
                return;

            TM.ReleaseTexture(tilesetID);
            tilesetFilePath = "";
            tilesetID = currTile = -1;

            // Empty out the undo/redo list
            Stack<CTile> undoMoves = new Stack<CTile>();
            Stack<CTile> redoMoves = new Stack<CTile>();

            // resize the form so the tileset isn't there
            MainForm.ActiveForm.Size = new Size(934, 612);
            deleteTilesetMenuItem.Enabled = false;
        }
        private void saveMenuItem_Click(object sender, EventArgs e)
        {
            ///////////////////////////////////////////////
            //                File Format                //
            ///////////////////////////////////////////////
            //- version          - float  (4 bytes)      //
            //- tileSetID        - int    (4 bytes)      //
            //- Filepath length  - int    (4 bytes)      //
            //- tileset Filepath - string (? bytes)      //
            //- Map Width        - int    (4 bytes)      //
            //- Map Height       - int    (4 bytes)      //
            //- Tile Width       - int    (4 bytes)      //
            //- Tile Height      - int    (4 bytes)      //
            //                                           //
            //  mapData[]                                //
            //  for (int x = 0; x < MapWidth; ++x)       //
            //      for (int y = 0; y < MapHeight; ++y)  //
            //                                           //
            //- tileImageID      - int    (4 bytes)      //
            //- isCollidable     - bool   (1 byte)       //
            ///////////////////////////////////////////////


            SaveFileDialog sfd = new SaveFileDialog();

            // Set the filter strings
            sfd.Filter = "TLOW Map(*.TLOWM)|*.TLOWM;";
            sfd.DefaultExt = "TLOWM";

            if (DialogResult.OK == sfd.ShowDialog())
            {
                FileStream stream = new FileStream(sfd.FileName, FileMode.Create);
                BinaryWriter binWriter = new BinaryWriter(stream);

                int nXTiles = mapData.GetLength(0);
                int nYTiles = mapData.GetLength(1);

                binWriter.Write(version);
                binWriter.Write(tilesetID);
                if (tilesetID != -1)
                {
                    binWriter.Write(tilesetFilePath.Length);
                    binWriter.Write(tilesetFilePath);
                }
                
                binWriter.Write(nXTiles);
                binWriter.Write(nYTiles);
                binWriter.Write(tile_width_height.Width);
                binWriter.Write(tile_width_height.Height);


                for (int x = 0; x < nXTiles; ++x)
                {
                    for (int y = 0; y < nYTiles; ++y)
                    {
                        binWriter.Write(mapData[x, y].TileImageID);
                        binWriter.Write(mapData[x, y].IsCollidable);
                    }
                }
                binWriter.Close();
                stream.Close();
                bIsSaved = true;
            }
        }
        private void exitMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void undoMenuItem_Click(object sender, EventArgs e)
        {
            // undo the last thing pushed on the stack
            // push it on the redo list
            CTile tempTile = undoMoves.Pop();

            // if it was added then remove it from the map
            // if it was removed then add it back to the map
            if (tempTile.WasAdded)
            {
                tempTile.WasAdded = false;
                mapData[tempTile.Location.X, tempTile.Location.Y] = new CTile();
            }
            else
            {
                tempTile.WasAdded = true;
                mapData[tempTile.Location.X, tempTile.Location.Y] = tempTile;
            }

            redoMoves.Push(tempTile);
        }
        private void redoMenuItem_Click(object sender, EventArgs e)
        {
            // redo the last thing pushed on the stack
            // push it on the undo list
            CTile tempTile = redoMoves.Pop();

            // if it was added then remove it from the map
            // if it was removed then add it back to the map
            if (tempTile.WasAdded)
            {
                tempTile.WasAdded = false;
                mapData[tempTile.Location.X, tempTile.Location.Y] = new CTile();
            }
            else
            {
                tempTile.WasAdded = true;
                mapData[tempTile.Location.X, tempTile.Location.Y] = tempTile;
            }

            undoMoves.Push(tempTile);
        }
        private void gridLinesMenuItem_Click(object sender, EventArgs e)
        {
            gridLinesMenuItem.Checked = !gridLinesMenuItem.Checked;
            bHasGridLines = !bHasGridLines;
        }
        private void aboutMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("The Legend of Wumbo - Version 1.0", "TLOW Map Editor", MessageBoxButtons.OK);
        }
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!bIsSaved)
            {
                this.DialogResult = MessageBox.Show("You have not saved your level, would you like to save before exiting?",
                 "Last Chance to Save!", MessageBoxButtons.YesNo);

                if (DialogResult.Yes == this.DialogResult)
                    // save then quit application
                    saveMenuItem_Click(sender, e);
            }

            if (tilesetID != -1)
                TM.ReleaseTexture(tilesetID);
        }

        // RENDER MAP AND TILESET //
        public void RenderMapViewport()
        {
            D3D.Clear(128, 128, 128);
            D3D.DeviceBegin();
            D3D.SpriteBegin();


            int xTiles = mapData.GetLength(0);
            int yTiles = mapData.GetLength(1);

            Point tempMapOffset = new Point();
            tempMapOffset.X -= mapHScrollBar.Value;
            tempMapOffset.Y -= mapVScrollBar.Value;
            mapOffset = tempMapOffset;

            Rectangle mapTile;
            // draw the map tiles
            for (int x = 0; x < xTiles; ++x)
            {
                for (int y = 0; y < yTiles; ++y)
                {
                    // there is a tile image
                    if (mapData[x, y].TileImageID != -1 && tilesetID != -1)
                    {
                        int tilesPerRow = tilesetImage.Width / tile_width_height.Width;
                        int currRow = mapData[x, y].TileImageID / tilesPerRow;
                        int currCol = mapData[x, y].TileImageID - (currRow * tilesPerRow);

                        int Ypos = currRow * tile_width_height.Height;
                        int Xpos = currCol * tile_width_height.Width;

                        mapTile = new Rectangle(Xpos + mapOffset.X, Ypos + mapOffset.Y,
                                                    tile_width_height.Width, tile_width_height.Height);

                        TM.Draw(tilesetID, x * tile_width_height.Width + mapOffset.X,
                                    y * tile_width_height.Height + mapOffset.Y, 1.0f, 1.0f,
                                            mapTile, 0, 0, 0, Color.Empty.ToArgb());
                    }
                    else
                    {
                        mapTile = new Rectangle(x * tile_width_height.Width + mapOffset.X,
                                                  y * tile_width_height.Height + mapOffset.Y,
                                                    tile_width_height.Width, tile_width_height.Height);

                        if (tilesetID == -1 && mapData[x, y].TileImageID != -1)
                            D3D.DrawRect(mapTile, 255, 0, 0);
                        else 
                            D3D.DrawRect(mapTile, 0, 255, 0);
                    }
                }
            }


            D3D.Sprite.Flush();


            if (bHasGridLines)
            {
                DrawLines(tilesetImage, tile_width_height, mapData.GetLength(0), mapData.GetLength(1),
                               map_Panel.Size, mapOffset, map_width_height);
            }


            D3D.SpriteEnd();
            D3D.DeviceEnd();
            D3D.Present();

            if (tilesetID != -1)
            {
                // allow the tileset_Panel to display
                D3D.ChangeDisplayParam(tileset_Panel, true);
                RenderTilesetViewport();
            }
        }
        public void RenderTilesetViewport()
        {
            D3D.Clear(128, 128, 128);
            D3D.DeviceBegin();
            D3D.SpriteBegin();

            Point tempTilesetOffset = new Point();
            tempTilesetOffset.X -= tilesetHScrollBar.Value;
            tempTilesetOffset.Y -= tilesetVScrollBar.Value;
            tilesetOffset = tempTilesetOffset;

            if (tilesetID != -1)
            {
                // draw the tileset
                Rectangle rect = new Rectangle(0, 0, tilesetImage.Width, tilesetImage.Height);

                TM.Draw(tilesetID, tilesetOffset.X, tilesetOffset.Y, 1.0f, 1.0f, rect, 0, 0, 0, 0);


                // allow the lines to draw over the tileset
                D3D.Sprite.Flush();


                int nWidthTiles = tilesetImage.Width / tile_width_height.Width;
                int nHeightTiles = tilesetImage.Height / tile_width_height.Height;
                DrawLines(tilesetImage, tile_width_height, nWidthTiles, nHeightTiles,
                                        tileset_Panel.Size, tilesetOffset, tilesetImage.Size);


                D3D.Sprite.Flush();


                // draw rect around selected tile
                #region CURR_TILE_RECT
                if (currTile != -1)
                {
                    // ex. (tile 6) / (4 tiles per row) = row 1 ( rows start from row 0 )
                    int currRow = currTile / nWidthTiles;

                    // ex. (tile 6) - (row 1 * 4 tiles per row) = col 2 ( cols start from 0 )
                    int currCol = currTile - (currRow * nWidthTiles);

                    int Ypos = currRow * tile_width_height.Height;
                    int Xpos = currCol * tile_width_height.Width;

                    // draw a rect around the tile
                    D3D.DrawLine(Xpos + tilesetOffset.X, Ypos + tilesetOffset.Y, Xpos + tile_width_height.Width + tilesetOffset.X, Ypos + tilesetOffset.Y, 255, 0, 255);
                    D3D.DrawLine(Xpos + tile_width_height.Width + tilesetOffset.X, Ypos + tilesetOffset.Y, Xpos + tile_width_height.Width + tilesetOffset.X, Ypos + tile_width_height.Height + tilesetOffset.Y, 255, 0, 255);
                    D3D.DrawLine(Xpos + tilesetOffset.X, Ypos + tile_width_height.Height + tilesetOffset.Y, Xpos + tile_width_height.Width + tilesetOffset.X, Ypos + tile_width_height.Height + tilesetOffset.Y, 255, 0, 255);
                    D3D.DrawLine(Xpos + tilesetOffset.X, Ypos + tilesetOffset.Y, Xpos + tilesetOffset.X, Ypos + tile_width_height.Height + tilesetOffset.Y, 255, 0, 255);
                }
                #endregion
            }


            D3D.SpriteEnd();
            D3D.DeviceEnd();
            D3D.Present();


            // allow the map_Panel to display
            D3D.ChangeDisplayParam(map_Panel, true);
        }

        // DRAW HORIZONTAL AND VERTICAL LINES //
        private void DrawLines(Bitmap Image, Size tileSize, int nXLines, int nYLines, Size Panel, Point Offset, Size GridSize)
        {
            // add 1 to each so that there 
            // will be a line at the the end
            ++nXLines;
            ++nYLines;


            // draw horizonal lines
            for (int y = 0; y < nYLines; ++y)
            {
                // if there is less to display then the panel width draw until the image's width
                if (GridSize.Width + Offset.X < Panel.Width)
                    D3D.DrawLine(Offset.X, y * tileSize.Height + Offset.Y, GridSize.Width + Offset.X, y * tileSize.Height + Offset.Y, 0, 0, 255);
                else
                    D3D.DrawLine(Offset.X, y * tileSize.Height + Offset.Y, Panel.Width, y * tileSize.Height + Offset.Y, 0, 0, 255);                     
            }

            // draw vertical lines
            for (int x = 0; x < nXLines; ++x)
            {
                // if there is less to display then the panel height draw until the image's height
                if (GridSize.Height + Offset.Y < Panel.Height)
                    D3D.DrawLine(x * tileSize.Width + Offset.X, Offset.Y, x * tileSize.Width + Offset.X, GridSize.Height + Offset.Y, 0, 0, 255);
                else
                    D3D.DrawLine(x * tileSize.Width + Offset.X, Offset.Y, x * tileSize.Width + Offset.X, Panel.Height, 0, 0, 255);
            }
        }

        // MAP AND TILESET CLICK //
        private void tileset_Panel_MouseClick(object sender, MouseEventArgs e)
        {
            if (tilesetID != -1)
            {
                // find out where they clicked on the tileset
                if (e.Location.X - tilesetOffset.X > 0 && e.Location.X - tilesetOffset.X < tilesetImage.Width &&
                    e.Location.Y - tilesetOffset.Y > 0 && e.Location.Y - tilesetOffset.Y < tilesetImage.Height)
                {
                    int col = (e.Location.X - tilesetOffset.X) / tile_width_height.Width;
                    int row = (e.Location.Y - tilesetOffset.Y) / tile_width_height.Height;
                    int tile = (row * (tilesetImage.Width / tile_width_height.Width)) + col;

                    if (e.Button == MouseButtons.Left)
                    {
                        // select the tile on the tileset
                        currTile = tile;
                    }
                }
            }
        }
        private void map_Panel_MouseClick(object sender, MouseEventArgs e)
        {
           // find out where they clicked on the map
            if (e.Location.X - mapOffset.X > 0 && e.Location.X - mapOffset.X < map_width_height.Width &&
                e.Location.Y - mapOffset.Y > 0 && e.Location.Y - mapOffset.Y < map_width_height.Height)
            {
                int col = (e.Location.X - mapOffset.X) / tile_width_height.Width;
                int row = (e.Location.Y - mapOffset.Y) / tile_width_height.Height;
                CTile tempTile;
                
                if (e.Button == MouseButtons.Left)
                {
                    // only add if it is currently empty or not the same ID
                    if (mapData[col, row].TileImageID == -1 || mapData[col, row].TileImageID != currTile)
                    {
                        if (tilesetID == -1)
                            currTile = 0;

                        // paint the selected tile
                        tempTile = new CTile(currTile, isCollidableBox.Checked, true);
                        tempTile.Location = new Point(col, row);
                        mapData[col, row] = tempTile;
                        undoMoves.Push(tempTile);
                    }
                }
                else if (e.Button == MouseButtons.Right)
                {
                    // only delete if it is currently taken
                    if (mapData[col, row].TileImageID != -1)
                    {
                        tempTile = mapData[col, row];
                        tempTile.WasAdded = false;
                        mapData[col, row] = new CTile();
                        undoMoves.Push(tempTile);
                    }
                }
                else if (e.Button == MouseButtons.Middle)
                {
                    // see if a tile was set as collidable
                    isCollidableBox.Checked = mapData[col, row].IsCollidable;
                }
                bIsSaved = false;
            }
        }

        // UPDATE UNDO/REDO AND MAP RESIZE //
        public void UpdateObjects()
        {
            if (undoMoves.Count != 0)
                undoMenuItem.Enabled = true;
            else
                undoMenuItem.Enabled = false;


            if (redoMoves.Count != 0)
                redoMenuItem.Enabled = true;
            else
                redoMenuItem.Enabled = false;
        }
        private void resizeMapAndTiles_Click(object sender, EventArgs e)
        {
            int nMapWidth = (int)mapWidth.Value;
            int nMapHeight = (int)mapHeight.Value;
            CTile[,] tempData = new CTile[nMapWidth, nMapHeight];

            for (int x = 0; x < nMapWidth; ++x)
            {
                for (int y = 0; y < nMapHeight; ++y)
                {
                    if (x < mapData.GetLength(0) && y < mapData.GetLength(1))
                        tempData[x, y] = mapData[x, y];
                    else
                        tempData[x, y] = new CTile();
                }
            }

            mapData = tempData;
            tile_width_height = new Size((int)tileWidth.Value, (int)tileHeight.Value);
            map_width_height = new Size(mapData.GetLength(0) * tile_width_height.Width, mapData.GetLength(1) * tile_width_height.Height);
            
            // Scrollbar offsets //
            mapHScrollBar.Maximum = map_width_height.Width;
            mapVScrollBar.Maximum = map_width_height.Height;

            if (tilesetID != -1)
            {
                tilesetHScrollBar.Maximum = tilesetImage.Width;
                tilesetVScrollBar.Maximum = tilesetImage.Height;
            }
        }
    }
}